Skip to content

chore(heap-profiling): address heap-profiling feedback [PROF-15190]#2226

Merged
gh-worker-dd-mergequeue-cf854d[bot] merged 4 commits into
mainfrom
chore/sgg-address-review-feedback
Jul 14, 2026
Merged

chore(heap-profiling): address heap-profiling feedback [PROF-15190]#2226
gh-worker-dd-mergequeue-cf854d[bot] merged 4 commits into
mainfrom
chore/sgg-address-review-feedback

Conversation

@scottgerring

@scottgerring scottgerring commented Jul 10, 2026

Copy link
Copy Markdown
Member

What does this PR do?

Clarifies a bunch of stuff that was not obvious, and improves testing, addressing feedback from @danielsn on #2166

Motivation

What inspired you to submit this pull request?

Additional Notes

Anything else we should know when reviewing?

How to test the change?

Describe here in detail how the change can be validated.

@datadog-datadog-prod-us1-2

datadog-datadog-prod-us1-2 Bot commented Jul 10, 2026

Copy link
Copy Markdown

Tests

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

🎯 Code Coverage (details)
Patch Coverage: 61.11%
Overall Coverage: 74.44% (-0.03%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 0ac4b1e | Docs | Datadog PR Page | Give us feedback!

@scottgerring scottgerring force-pushed the chore/sgg-address-review-feedback branch from dc23ce8 to 16ec8ec Compare July 10, 2026 13:39
@pr-commenter

pr-commenter Bot commented Jul 10, 2026

Copy link
Copy Markdown

Benchmarks

Comparison

Benchmark execution time: 2026-07-14 06:49:43

Comparing candidate commit 0ac4b1e in PR branch chore/sgg-address-review-feedback with baseline commit 01f42a6 in branch main.

Found 0 performance improvements and 10 performance regressions! Performance is the same for 132 metrics, 0 unstable metrics.

Explanation

This is an A/B test comparing a candidate commit's performance against that of a baseline commit. Performance changes are noted in the tables below as:

  • 🟩 = significantly better candidate vs. baseline
  • 🟥 = significantly worse candidate vs. baseline

We compute a confidence interval (CI) over the relative difference of means between metrics from the candidate and baseline commits, considering the baseline as the reference.

If the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD), the change is considered significant.

Feel free to reach out to #apm-benchmarking-platform on Slack if you have any questions.

More details about the CI and significant changes

You can imagine this CI as a range of values that is likely to contain the true difference of means between the candidate and baseline commits.

CIs of the difference of means are often centered around 0%, because often changes are not that big:

---------------------------------(------|---^--------)-------------------------------->
                              -0.6%    0%  0.3%     +1.2%
                                 |          |        |
         lower bound of the CI --'          |        |
sample mean (center of the CI) -------------'        |
         upper bound of the CI ----------------------'

As described above, a change is considered significant if the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD).

For instance, for an execution time metric, this confidence interval indicates a significantly worse performance:

----------------------------------------|---------|---(---------^---------)---------->
                                       0%        1%  1.3%      2.2%      3.1%
                                                  |   |         |         |
       significant impact threshold --------------'   |         |         |
                      lower bound of CI --------------'         |         |
       sample mean (center of the CI) --------------------------'         |
                      upper bound of CI ----------------------------------'

scenario:profiles_dictionary/profile_string_inserts/threads/1

  • 🟥 execution_time [+13.210µs; +16.883µs] or [+4.347%; +5.556%]
  • 🟥 throughput [-179437.162op/s; -139024.609op/s] or [-5.324%; -4.125%]

scenario:vec_map/contains_key/128

  • 🟥 execution_time [+943.237ns; +951.333ns] or [+6.290%; +6.344%]
  • 🟥 throughput [-509372.357op/s; -505059.897op/s] or [-5.967%; -5.917%]

scenario:vec_map/contains_key/16

  • 🟥 execution_time [+270.078ns; +270.366ns] or [+117.146%; +117.271%]
  • 🟥 throughput [-37489961.161op/s; -37410226.637op/s] or [-54.019%; -53.905%]

scenario:vec_map/contains_key/64

  • 🟥 execution_time [+507.669ns; +510.472ns] or [+12.883%; +12.954%]
  • 🟥 throughput [-1863269.599op/s; -1852944.026op/s] or [-11.472%; -11.409%]

scenario:vec_map/contains_key/8

  • 🟥 execution_time [+139.099ns; +139.215ns] or [+197.193%; +197.358%]
  • 🟥 throughput [-75353508.489op/s; -75175938.427op/s] or [-66.440%; -66.284%]

Benchmark execution time: 2026-07-14 07:04:45

Comparing candidate commit 0ac4b1e in PR branch chore/sgg-address-review-feedback with baseline commit 01f42a6 in branch main.

Found 2 performance improvements and 17 performance regressions! Performance is the same for 158 metrics, 10 unstable metrics.

Explanation

This is an A/B test comparing a candidate commit's performance against that of a baseline commit. Performance changes are noted in the tables below as:

  • 🟩 = significantly better candidate vs. baseline
  • 🟥 = significantly worse candidate vs. baseline

We compute a confidence interval (CI) over the relative difference of means between metrics from the candidate and baseline commits, considering the baseline as the reference.

If the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD), the change is considered significant.

Feel free to reach out to #apm-benchmarking-platform on Slack if you have any questions.

More details about the CI and significant changes

You can imagine this CI as a range of values that is likely to contain the true difference of means between the candidate and baseline commits.

CIs of the difference of means are often centered around 0%, because often changes are not that big:

---------------------------------(------|---^--------)-------------------------------->
                              -0.6%    0%  0.3%     +1.2%
                                 |          |        |
         lower bound of the CI --'          |        |
sample mean (center of the CI) -------------'        |
         upper bound of the CI ----------------------'

As described above, a change is considered significant if the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD).

For instance, for an execution time metric, this confidence interval indicates a significantly worse performance:

----------------------------------------|---------|---(---------^---------)---------->
                                       0%        1%  1.3%      2.2%      3.1%
                                                  |   |         |         |
       significant impact threshold --------------'   |         |         |
                      lower bound of CI --------------'         |         |
       sample mean (center of the CI) --------------------------'         |
                      upper bound of CI ----------------------------------'

scenario:alloc_free/sampled_noop_slow_path/16

  • 🟥 execution_time [+14.793ns; +14.829ns] or [+23.832%; +23.889%]

scenario:alloc_free/sampled_noop_slow_path/256

  • 🟥 execution_time [+14.782ns; +14.812ns] or [+23.809%; +23.857%]

scenario:alloc_free/sampled_noop_slow_path/4096

  • 🟥 execution_time [+14.824ns; +14.863ns] or [+23.742%; +23.805%]

scenario:alloc_free/sampled_noop_slow_path/64

  • 🟥 execution_time [+15.178ns; +15.222ns] or [+24.614%; +24.685%]

scenario:alloc_free/sampled_noop_slow_path/65536

  • 🟥 execution_time [+15.164ns; +15.206ns] or [+22.781%; +22.844%]

scenario:alloc_free/sampled_system_fast_path/4096

  • 🟥 execution_time [+4.551ns; +4.716ns] or [+4.739%; +4.911%]

scenario:alloc_free/sampled_system_slow_path/4096

  • 🟥 execution_time [+6.114ns; +6.305ns] or [+4.049%; +4.175%]

scenario:glob_matcher/ascii_wildcard_question_match/wall_time

  • 🟩 execution_time [-21.408ns; -21.387ns] or [-36.751%; -36.714%]

scenario:glob_matcher/ascii_wildcard_star_match/wall_time

  • 🟩 execution_time [-20.667ns; -20.646ns] or [-35.478%; -35.442%]

scenario:sampler_only/fast_path/16

  • 🟥 execution_time [+12.054ns; +12.062ns] or [+88.171%; +88.231%]

scenario:sampler_only/fast_path/256

  • 🟥 execution_time [+12.052ns; +12.063ns] or [+88.155%; +88.236%]

scenario:sampler_only/fast_path/4096

  • 🟥 execution_time [+12.052ns; +12.060ns] or [+88.155%; +88.214%]

scenario:sampler_only/fast_path/64

  • 🟥 execution_time [+12.053ns; +12.062ns] or [+88.161%; +88.223%]

scenario:sampler_only/fast_path/65536

  • 🟥 execution_time [+12.057ns; +12.065ns] or [+88.198%; +88.253%]

scenario:sampler_only/slow_path/16

  • 🟥 execution_time [+13.662ns; +13.693ns] or [+22.512%; +22.563%]

scenario:sampler_only/slow_path/256

  • 🟥 execution_time [+13.681ns; +13.708ns] or [+22.539%; +22.584%]

scenario:sampler_only/slow_path/4096

  • 🟥 execution_time [+13.689ns; +13.727ns] or [+22.424%; +22.486%]

scenario:sampler_only/slow_path/64

  • 🟥 execution_time [+13.669ns; +13.704ns] or [+22.522%; +22.580%]

scenario:sampler_only/slow_path/65536

  • 🟥 execution_time [+14.042ns; +14.085ns] or [+21.566%; +21.631%]

Candidate

Omitted due to size.

Baseline

Omitted due to size.

@scottgerring scottgerring marked this pull request as ready for review July 10, 2026 13:52
@scottgerring scottgerring requested a review from a team as a code owner July 10, 2026 13:52
@scottgerring scottgerring requested a review from danielsn July 10, 2026 13:54
@scottgerring scottgerring force-pushed the chore/sgg-address-review-feedback branch from 16ec8ec to b7f1281 Compare July 10, 2026 14:13
@dd-octo-sts

dd-octo-sts Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Artifact Size Benchmark Report

aarch64-alpine-linux-musl
Artifact Baseline Commit Change
/aarch64-alpine-linux-musl/lib/libdatadog_profiling.so 7.88 MB 7.88 MB 0% (0 B) 👌
/aarch64-alpine-linux-musl/lib/libdatadog_profiling.a 86.15 MB 86.15 MB 0% (0 B) 👌
aarch64-unknown-linux-gnu
Artifact Baseline Commit Change
/aarch64-unknown-linux-gnu/lib/libdatadog_profiling.so 10.62 MB 10.62 MB 0% (0 B) 👌
/aarch64-unknown-linux-gnu/lib/libdatadog_profiling.a 97.40 MB 97.40 MB 0% (0 B) 👌
libdatadog-x64-windows
Artifact Baseline Commit Change
/libdatadog-x64-windows/debug/dynamic/datadog_profiling_ffi.dll 25.54 MB 25.54 MB 0% (0 B) 👌
/libdatadog-x64-windows/debug/dynamic/datadog_profiling_ffi.lib 88.44 KB 88.44 KB 0% (0 B) 👌
/libdatadog-x64-windows/debug/dynamic/datadog_profiling_ffi.pdb 185.32 MB 185.32 MB 0% (0 B) 👌
/libdatadog-x64-windows/debug/static/datadog_profiling_ffi.lib 957.25 MB 957.25 MB 0% (0 B) 👌
/libdatadog-x64-windows/release/dynamic/datadog_profiling_ffi.dll 8.35 MB 8.35 MB 0% (0 B) 👌
/libdatadog-x64-windows/release/dynamic/datadog_profiling_ffi.lib 88.44 KB 88.44 KB 0% (0 B) 👌
/libdatadog-x64-windows/release/dynamic/datadog_profiling_ffi.pdb 24.72 MB 24.72 MB 0% (0 B) 👌
/libdatadog-x64-windows/release/static/datadog_profiling_ffi.lib 49.21 MB 49.21 MB 0% (0 B) 👌
libdatadog-x86-windows
Artifact Baseline Commit Change
/libdatadog-x86-windows/debug/dynamic/datadog_profiling_ffi.dll 22.19 MB 22.19 MB 0% (0 B) 👌
/libdatadog-x86-windows/debug/dynamic/datadog_profiling_ffi.lib 89.82 KB 89.82 KB 0% (0 B) 👌
/libdatadog-x86-windows/debug/dynamic/datadog_profiling_ffi.pdb 189.78 MB 189.78 MB 0% (0 B) 👌
/libdatadog-x86-windows/debug/static/datadog_profiling_ffi.lib 945.93 MB 945.93 MB 0% (0 B) 👌
/libdatadog-x86-windows/release/dynamic/datadog_profiling_ffi.dll 6.46 MB 6.46 MB 0% (0 B) 👌
/libdatadog-x86-windows/release/dynamic/datadog_profiling_ffi.lib 89.82 KB 89.82 KB 0% (0 B) 👌
/libdatadog-x86-windows/release/dynamic/datadog_profiling_ffi.pdb 26.54 MB 26.54 MB 0% (0 B) 👌
/libdatadog-x86-windows/release/static/datadog_profiling_ffi.lib 46.82 MB 46.82 MB 0% (0 B) 👌
x86_64-alpine-linux-musl
Artifact Baseline Commit Change
/x86_64-alpine-linux-musl/lib/libdatadog_profiling.a 76.97 MB 76.97 MB 0% (0 B) 👌
/x86_64-alpine-linux-musl/lib/libdatadog_profiling.so 8.81 MB 8.81 MB 0% (0 B) 👌
x86_64-unknown-linux-gnu
Artifact Baseline Commit Change
/x86_64-unknown-linux-gnu/lib/libdatadog_profiling.a 92.38 MB 92.38 MB 0% (0 B) 👌
/x86_64-unknown-linux-gnu/lib/libdatadog_profiling.so 10.72 MB 10.72 MB 0% (0 B) 👌

@scottgerring scottgerring requested a review from a team as a code owner July 13, 2026 12:52
@scottgerring scottgerring force-pushed the chore/sgg-address-review-feedback branch from fa0ea0a to 9430bef Compare July 13, 2026 12:53

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refines the heap-profiling sampler/gotter implementation based on feedback from #2166, focusing on clarifying invariants, reducing allocator-side footguns (size/header drift, errno perturbation), and strengthening integration tests that validate real shipped artifacts.

Changes:

  • Added an opt-in Linux sanity-check feature to self-inspect ELF .note.stapsdt and assert exactly one USDT note per expected ddheap probe.
  • Centralized x86 sampled-allocation header and bumped-size logic into shared inlines to prevent alloc/free formula drift; renamed dd_sample_flag_checkdd_sample_flag_check_and_clear.
  • Improved operational robustness and testability: preserve errno around probes/logging/init, add gotter rescan coordination, and expand install/behavior integration tests (including USDT-note checks).

Reviewed changes

Copilot reviewed 29 out of 32 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
libdd-profiling-heap-sampler/src/usdt_check.rs New ELF self-inspection utility for “one note per USDT probe” invariants (opt-in).
libdd-profiling-heap-sampler/src/tl_state.c Preserve errno across first-touch TLS init work.
libdd-profiling-heap-sampler/src/sample_flag.c Align implementation with new header helpers and renamed check API.
libdd-profiling-heap-sampler/src/probes.c Preserve errno across USDT probe emission.
libdd-profiling-heap-sampler/src/lib.rs Export usdt_check behind feature; update test to new check API.
libdd-profiling-heap-sampler/src/generated/dd_heap_sampler_static_wrappers.c Expose new predicate and renamed check for Rust bindings.
libdd-profiling-heap-sampler/src/generated/bindings.rs Bind new dd_alloc_req_is_sampled and renamed dd_sample_flag_check_and_clear.
libdd-profiling-heap-sampler/src/allocation_requested.c Use shared x86 bumped-size helper; preserve errno around sampling/logging.
libdd-profiling-heap-sampler/src/allocation_realloc.c Refactor realloc prepare/commit to use shared header helpers and clearer control flow.
libdd-profiling-heap-sampler/src/allocation_freed.c Use shared bumped-size helper to keep sized-free consistent with alloc side.
libdd-profiling-heap-sampler/src/allocation_created.c Add debug assertion around reentry-guard pairing; improve commentary.
libdd-profiling-heap-sampler/include/datadog/heap/sample_flag.h Add shared x86 size/header helpers; rename check API; consolidate stamping/clearing.
libdd-profiling-heap-sampler/include/datadog/heap/allocation_requested.h Add dd_alloc_req_is_sampled predicate and clarify comments/includes.
libdd-profiling-heap-sampler/include/datadog/heap/allocation_freed.h Update docs and switch to dd_sample_flag_check_and_clear.
libdd-profiling-heap-sampler/include/datadog/heap/allocation_created.h Use the new predicate helper instead of raw weight comparison.
libdd-profiling-heap-sampler/docs/tagging.md Update documentation to match centralized sizing/header logic.
libdd-profiling-heap-sampler/Cargo.toml Add opt-in sanity-check feature and optional deps (elf, anyhow).
libdd-profiling-heap-sampler/build.rs Define NDEBUG for optimized builds to strip C asserts in release.
libdd-profiling-heap-gotter/tests/install.rs Strengthen GOT install tests; add isolation warning and errno passthrough checks.
libdd-profiling-heap-gotter/src/lib.rs Add pending-rescan coordination to avoid missing raced dlopen updates; add test hook-hit accessor.
libdd-profiling-heap-gotter/src/hooks.rs Use new predicate helper; add test hook-hit counter and tweak alignment calc.
libdd-profiling-heap-gotter/src/elf.rs Document a known limitation in patched-library identity detection.
libdd-profiling-heap-gotter/Cargo.toml Add test-support feature for exposing test-only hook-hit counter.
libdd-profiling-heap-gotter-ffi/tests/usdt_notes.rs New integration test validating USDT note uniqueness in the built cdylib.
libdd-profiling-heap-gotter-ffi/tests/install.rs Optionally assert hooks actually ran (via test-support hook-hit counter).
libdd-profiling-heap-gotter-ffi/src/lib.rs Export test-only ddog_heap_gotter_test_hook_hits behind feature gate.
libdd-profiling-heap-gotter-ffi/examples/cdylib_demo.rs Improve demo documentation and safety notes for dlsym transmute.
libdd-profiling-heap-gotter-ffi/Cargo.toml Add test-support feature passthrough and dev-dep enabling sampler sanity-check.
libdd-profiling-heap-allocator/src/lib.rs Clarify live-heap feature documentation.
libdd-profiling-heap-allocator/src/allocator.rs Clarify alignment rationale when freeing sampled allocations.
libdd-profiling-heap-allocator/benches/sampler_overhead.rs Add safety/docs and clarify benchmark intent and constants.
Cargo.lock Add new optional dependencies for sanity-check feature.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread libdd-profiling-heap-gotter/src/hooks.rs
// underlying allocator zeroes everything we hand back. Unsampled
// path keeps the user's (nmemb, size) verbatim.
let raw = if req.weight == 0 {
let raw = if !dd_alloc_req_is_sampled(req) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think this is a real tension. I've inlined it per this with a healthy comment on it to explain why. I've left the C refactoring because it is easier to read.

@danielsn danielsn left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some comments / questions but they're improvements, not things that would block merge.

Comment thread libdd-profiling-heap-gotter/src/lib.rs Outdated
fn lock_global_overrides() -> MutexGuard<'static, Option<SymbolOverrides>> {
GLOBAL_OVERRIDES
.lock()
// Recover from poison: hot path never takes this lock, registry re-applies idempotently.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I understand this argument

Comment thread libdd-profiling-heap-gotter/src/lib.rs Outdated
// Recover from poison: same rationale as lock_global_overrides.
Err(TryLockError::Poisoned(poisoned)) => poisoned.into_inner(),
Err(TryLockError::WouldBlock) => return,
// Lock held by another install/update: flag a rescan so that thread

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can there still be a TOCTTOU race here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah; i think it's rather unlikely but it could in principle happen, and saying "it resolves when the next library is dlopen'd" is cold comfort if you're loading the last library. I will just serialise the operation with a Mutex.

// our post-call bookkeeping. A huge aligned_alloc reliably fails with
// ENOMEM on every libc (size is a multiple of the alignment).
*libc::__errno_location() = 0;
let huge = usize::MAX & !0xfff;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason, I hadn't noticed that rust uses ! instead of ~ until now

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah tbh when I first noticed this it janked me out; I like the distinction between bitwise/logical in C for some reason 🤷

static inline __attribute__((always_inline))
x86_header_t x86_header_read(const void *user) {
x86_header_t hdr;
memcpy(&hdr, (const char *)user - DD_HEADER_BYTES, sizeof(hdr));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could elide the memcpy here with a pointer cast

return *(x86_header_t*)((const char *)user - DD_HEADER_BYTES));

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH I can see that the memcpy is almost certainly going to always work, but it feels like sailing a bit close to the wind with aliasing concerns. I had a look at the assembler and it gets optimised away anyway - e.g. here's x86 stamp header - its just two movs, one for the header and one for the offset.

   movabs  rcx, DD_MAGIC
   mov     qword ptr [rax - 0x10], rcx
   mov     qword ptr [rax - 0x8], rsi

/* Stamp the header in front of `user` (offset = user - raw). */
static inline __attribute__((always_inline))
void x86_header_stamp(void *user, uint64_t offset) {
x86_header_t hdr = { DD_MAGIC, offset };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could do the same pointer trick I suggest below to make this a one-liner, but not sure that's clearer.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we had x86_header_get, this would become

static inline __attribute__((always_inline))
void x86_header_stamp(void *user, uint64_t offset) {
    *x86_header_get(user) = (x86_header_t) { DD_MAGIC, offset };
}

Comment on lines +183 to +184
const x86_header_t zero = { 0, 0 };
memcpy((char *)user - DD_HEADER_BYTES, &zero, sizeof(zero));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

memset?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or

*x86_header_get(user) = (x86_header_t) { 0,0 };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or even

void x86_header_clear(x86_header_t *hdr) {
    *hdr = (x86_header_t) { 0,0 };
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the memset option - it keeps the "we're working with bytes" semantics but makes the intent more explicit.

Comment thread libdd-profiling-heap-sampler/include/datadog/heap/sample_flag.h
@gh-worker-dd-mergequeue-cf854d gh-worker-dd-mergequeue-cf854d Bot deleted the chore/sgg-address-review-feedback branch July 14, 2026 10:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants